########################
#
# Copyright 2011, Vanderbilt University
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an "AS IS"
# BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
# or implied.  See the License for the specific languaged governing 
# permissions and limitations under the License.
#
########################

###
# This file contains the searching strategies evaluated in
# our research.
#
#    BPS - Bisecting Policy Search.  The policy strings
#    are imagined to reside on a lattice.  The lattice is
#    traversed a la a binary tree until a boundary policy
#    is found.  This is a policy which is acceptable, but
#    has a one-level generalization that is unacceptable.
#
###
import time
import cPickle as pickle
from operator import or_
import policyEvaluation

"""
Dataset will appear in a pickled form, already translated
into the language of the Population Statistics, P

ds

Also, will need a pickled form of the prior searches/
prior queries.

prior

Each search is an object with its own dataset ds,
a variable indicating what the current policy is.

"""    
class Search():
    def __init__(self, fields, vocab, dset, popStats, prior, description="", threshold = 0, numToFind=1):
        self.numToFind=numToFind 
        self.T = threshold
        self.description = description
        self.solutionPolicies = []
        self.nodesSearched = None
        self.nodesEvaluated = None
        self.searchPaths=[]
        self.e = policyEvaluation.Evaluator(vocab, fields, dset, prior, popStats)
        self.polSize = len(vocab) - len(fields) + 1
    def search(self):
        pass
    def isPolicyAcceptable(self, newPolicy):
        """
        policy is acceptable if a specification is acceptable.
        Policy is not acceptable if a generalization is not acceptable.
        Otherwise, must evaluate the policy itself
        
        """
        if newPolicy in self.previousPolicies:
            return self.previousPolicies <= self.cutoffValue
        if reduce(or_, [policyEvaluation.PolicyString.isSpecification(p, newPolicy) and \
                        self.previousPolicies[p] <= self.cutoffValue \
                        for p in self.previousPolicies], False):
            return True
        if reduce(or_, [policyEvaluation.PolicyString.isGeneralization(p, newPolicy) and \
                        self.previousPolicies[p] > self.cutoffValue \
                        for p in self.previousPolicies], False):
            return False
        else:
            """ evaluate the policy """
            value = self.evaluatePolicy(newPolicy) 
            self.nodesEvaluated += 1
            self.previousPolicies[newPolicy] = value
            return value <= self.cutoffValue 
    def policyCutoff(self, policy):
        self.T = self.evaluatePolicy(policy)
    def saveState(self):
        p = open("temp/prior.pckl", 'w')
        pickle.dump(self.prior, p)
        p.close()
    def interimReport(self):
        print "Have found %i solutions"%(len(self.solutionPolicies))
        print "Elapsed time %i minutes"%((time.time() - self.startTime)/60)
    def finalReport(self):
        import datetime
        elapsedTime = time.time() - self.startTime
        print "Found %i policies in %i minutes"%(len(self.solutionPolicies),
                                                 elapsedTime / 60)
        saveFilePath = "data/"+ datetime.datetime.now().strftime("%m-%d_%H:%M:%S")
        toSave = SearchSolution(self.ds.vocab, \
                         dict([(p, self.previousPolicies[p]) for p in self.solutionPolicies]), \
                         self.cutoffValue,\
                         elapsedTime, \
                         self.description, \
                         self.nodesSearched, \
                         self.nodesEvaluated,
                         self.searchPaths)
        toSave.save(saveFilePath)
        print "Details can be found at %s"%(saveFilePath) 

class BPS(Search):
    def __init__(self, fields, vocab, dset, popStats, prior, description="", threshold = 0, numToFind = 1, weightingFn = None):
        super(BPS, self).__init__(self, fields, vocab, dset, popStats, prior, description, threshold, numToFind)
        self.weightingFn = weightingFn
    def search(self):
        self.nodesSearched = 0
        self.nodesEvaluated = 0
        self.startTime = time.time()
        while (len(self.solutionPolicies) < self.numToFind) and \
              (time.time() - self.startTime < 86400):
            searchPath = []
            general = policyEvaluation.PolicyString.mostGeneral(self.polSize)
            specific= policyEvaluation.PolicyString.mostSpecific(self.polSize)
            while (policyEvaluation.PolicyString.levelsBetween(general, specific) > 2):
                searchPath.append((general, specific))
                newPolicy = policyEvaluation.PolicyString.halfpoint(general, specific, self.weightingFn(self.solutionPolicies, self.polSize))
                self.nodesSearched += 1
                isAcceptable = self.e.belowThreshold(newPolicy, self.T)
                if isAcceptable:
                    general = newPolicy
                else:
                    specific = newPolicy
            if self.isPolicyAcceptable(general):
                # if the most specific policy is acceptable, then there is only
                # one solution to find.  Add this policy and discontinue.
                if self.isPolicyAcceptable(specific):
                    self.solutionPolicies.append(specific)
                    break
                #test whether solution is dominating (in which case, remove
                #dominated policies), dominated (don't add), or stand-alone (add
                #to list of solutions)
                self.solutionPolicies.append(general)
                dominated = [p for p in self.solutionPolicies if policyEvaluation.PolicyString.isGen(p,general)]
                for p in dominated:
                    self.solutionPolicies.remove(p) 
                if reduce(or_, [policyEvaluation.PolicyString.isGen(general,p) for p in self.solutionPolicies]):
                    self.solutionPolicies.remove(general)
                    self.saveState()
            self.interimReport()
            self.searchPaths.append(searchPath)
        self.finalReport()
        
class SearchSolution():
    def __init__(self, vocabulary, solutions, cutoffValue, elapsedTime, desc, nodesSearched, nodesEvaluated, searchPaths):
        self.vocab = vocabulary
        self.solutions = solutions
        self.cutoffValue = cutoffValue
        self.elapsedTime = elapsedTime
        self.basicDescription = desc
        self.nodesSearched = nodesSearched
        self.nodesEvaluated = nodesEvaluated
        self.searchPaths = searchPaths
    def save(self, saveFilePath):
        s = open(saveFilePath, 'w')
        pickle.dump(self, s)
        s.close()

    
